package com.axapoint.opoc.core.dao;

import java.util.ArrayList;

import org.hibernate.Session;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import com.axapoint.opoc.core.UserException;
import com.axapoint.opoc.core.dao.ObjectRegistration._Value.Operation;
import com.axapoint.opoc.core.dao.RootUnitRelation.Value;
import com.axapoint.opoc.core.model.util.HibernateUtil;

public class UnitFacade extends AbstractFacade<Unit, Unit.Value, Unit.Bitemporal> {

	public UnitFacade(String systemId, String userId) {
		super(systemId, userId, new Unit(), new Unit.Value());
	}
	public UnitLocation.Value[] getUnitLocations(Unit e, DateTime at) {
		
		ArrayList<UnitLocation.Value> result = new ArrayList<UnitLocation.Value>();
        for (UnitLocation.Value v : e.getUnitLocations()) {
        	if (at == null || v.getBitemporal().getValidityInterval().contains(at)) {
        		result.add(v);
        	}
        }
		
		return result.toArray(new UnitLocation.Value[0]);
	}
	public void setUnitLocations(Unit e, UnitLocation.Value[] unitLocations, Interval interval) {
		UnitLocation.Value[] current = getUnitLocations(e, interval.getStart());
		ArrayList<UnitLocation.Value> changed = new ArrayList<UnitLocation.Value>();
		ArrayList<UnitLocation.Value> deleted = new ArrayList<UnitLocation.Value>();
		
		// Find differences
		int cnt = 0;
		for (UnitLocation.Value ulv : current) {
			if (unitLocations.length > cnt) {
				
			} else {
				
			// XXX:	changed.add();
			}
		}
		
		for (UnitLocation.Value ulv : changed) {
			ulv.getEntity().getValues().set(ulv, interval);
		}
		
		for (UnitLocation.Value ulv : deleted) {
			ulv.getEntity().getValues().end(interval.getStart());
		}
	}
	public void setOrganizationChannel(UnitLocation ul, OrganizationChannelType oct, OrganizationChannel oc, Interval interval) {
		// XXX: impl
	}
	public RootUnitRelation.Value[] getRootUnitRelations(Unit e, DateTime at) {
		
		ArrayList<RootUnitRelation.Value> result = new ArrayList<RootUnitRelation.Value>();
        for (RootUnitRelation.Value v : e.getRootUnitRelations()) {
        	if (at == null || v.getBitemporal().getValidityInterval().contains(at)) {
        		
        		result.add(v);
        	}
        }
		
		return result.toArray(new RootUnitRelation.Value[0]);
	}
	public Organization.Value getOrganization(Unit e, DateTime at) {
		UnitRelation ur = getUnitRelation(e);
		
		if (ur == null) return null;
		return ur.getValues().get(at).getValue().getOrganization().getValues().get(at).getValue();
	}
	public Unit.Value[] getChildren(Unit e, DateTime at, String orgUuid) {
		
		ArrayList<Unit.Value> result = new ArrayList<Unit.Value>();
        for (UnitRelation.Value v : e.getParentUnitRelations()) {
        	if ((orgUuid == null || v.getOrganization().getUuid().equals(orgUuid)) &&
        	    (at == null || v.getBitemporal().getValidityInterval().contains(at))) {
        		
        		result.add(v.getChild().getValues().get(at).getValue());
        	}
        }
		
		return result.toArray(new Unit.Value[0]);
	}
	public Unit.Value getParent(Unit e, DateTime at) {
		UnitRelation ur = getUnitRelation(e);
		
		if (ur == null) return null;
		return ur.getValues().get(at).getValue().getParent().getValues().get(at).getValue();
	}
	public void setChild(Unit unit, Unit child, UnitRelationType relationType, Organization org, Interval interval) {

		// Check if interval does not overlap org
		checkValidityInterval(interval, unit, child, relationType, org);
		
		UnitRelation newUr = new UnitRelation();
		HibernateUtil.getCurrentSession().save(newUr);
		UnitRelation.Value urv = new UnitRelation.Value(relationType, org, unit, child);
		urv = new UnitRelation.Value(newUr, getSystemId(), getUserId(), Operation.CREATE, "", urv);
		HibernateUtil.getCurrentSession().save(urv);
		
		newUr.getValues().set(urv, interval);
		HibernateUtil.getCurrentSession().save(newUr);
	}
	public void setParent(Unit unit, Unit parent, UnitRelationType relationType, Organization org, Interval interval) {
		UnitRelation ur = getUnitRelation(unit);

		if (ur == null) return;

		// Use same relation type and org as parent if they are null
		if (relationType == null) {
			relationType = ur.getValues().get(interval.getStart()).getValue().getType();
		}
		if (org == null) {
			org = ur.getValues().get(interval.getStart()).getValue().getOrganization();
		}
		
		// Check if interval does not overlap org
		checkValidityInterval(interval, unit, parent, org, relationType);
		
		// find unitrelation if it exists and add new value
		UnitRelation.Value urv = ur.getValues().get(interval.getStart()).getValue();
		
		UnitRelation newUr;
		if (urv == null) {
			newUr = new UnitRelation();
			HibernateUtil.getCurrentSession().save(newUr);
		} else {
			newUr = urv.getEntity();
		}
		urv = new UnitRelation.Value(relationType, org, parent, unit);
		urv = new UnitRelation.Value(newUr, getSystemId(), getUserId(), Operation.CREATE, "", urv);
		HibernateUtil.getCurrentSession().save(urv);
		
		newUr.getValues().set(urv, interval);
		HibernateUtil.getCurrentSession().save(newUr);
	}
	public void setAsRoot(Unit unit, Organization org, Interval interval) {
		Session session = HibernateUtil.getCurrentSession();
		
		// Check if interval does not overlap org
		checkValidityInterval(interval, unit, org);

		// Check if org already has a root
		RootUnitRelation rur = null;
		Operation operation = Operation.CREATE;
		if (org.getRootUnitRelations().size() > 0) {
			
			rur = org.getRootUnitRelations().toArray(new RootUnitRelation.Value[0])[0].getEntity();
			operation = Operation.EDIT;
		} else {
			rur = new RootUnitRelation();
			session.save(rur);
		}
		RootUnitRelation.Value rurv = new RootUnitRelation.Value(rur, getSystemId(), getUserId(), operation, "", new Value(org, unit));
		session.save(rurv);
			
		rur.getValues().set(rurv, interval);
		session.save(rur);
	}	
	public void move(Unit unit, Unit parent, Interval interval) {
		
		// Check if interval does not overlap org
		checkValidityInterval(interval, unit, parent);
		
		// Find the moving units unit relation
		UnitRelation ur = getUnitRelation(unit);
		UnitRelation.Value from = ur.getValues().get(interval.getStart()).getValue();
		
		// Set new parent 
		UnitRelation.Value to = new UnitRelation.Value(ur, getSystemId(), getUserId(), Operation.EDIT, "", new UnitRelation.Value(from.getType(), from.getOrganization(), parent, unit));
		ur.getValues().set(to, interval);
	}
	@Override
	public ObjectRegistration[] getAffectedEntities(String uuid, Interval newInterval) {
		ArrayList<ObjectRegistration> result = new ArrayList<ObjectRegistration>();
		
		Unit unit = getEntity(uuid);
		if (unit == null) throw new UserException("Ingen enhed med UUID: " + uuid);
		
		// Check unitlocations
		for (UnitLocation.Value l : unit.getUnitLocations()) {
			Interval i = l.getEntity().getValues().get(newInterval.getStart()).getValidityInterval();
			
			// If the unit location does not have interval this can contain new location it is affected
			if (!i.contains(newInterval)) {
				result.add(l.getEntity());
			}
		}
		// Check unitRelations
		for (UnitRelation.Value l : unit.getParentUnitRelations()) {
			Interval i = l.getEntity().getValues().get(newInterval.getStart()).getValidityInterval();
			
			// If the unit location does not have interval this can contain new location it is affected
			if (!i.contains(newInterval)) {
				result.add(l.getEntity());
			}
		}
		// Check unitRelations
		UnitRelation ur = getUnitRelation(unit);
		if (ur != null) {
			Interval i = ur.getValues().get(newInterval.getStart()).getValidityInterval();
			
			if (!i.contains(newInterval)) {
				result.add(ur);
			}
		}		
		return result.toArray(new ObjectRegistration[0]);
	}
	private UnitRelation getUnitRelation(Unit unit) {

		if (unit.getChildUnitRelations().size() > 0) {
			UnitRelation ur = unit.getChildUnitRelations().toArray(new UnitRelation.Value[0])[0].getEntity();
			return ur;
		}
		return null;
	}
}